/*
 * Copyright (c) 2020 Fraunhofer FOKUS and others. All rights reserved.
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contact: mosaic@fokus.fraunhofer.de
 */
package org.eclipse.mosaic.fed.mqtt.protocol;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * Mec protocol
 */
public class MecProtocol extends AbstractProtocol{

    private MecHeader header;

    private MecBody body;

    private long timestampOfDevOut;


    public MecProtocol() {

    }

    public MecProtocol(MecHeader header, MecBody body, long timestampOfDevOut) {
        this.header = header;
        this.body = body;
        this.timestampOfDevOut = timestampOfDevOut;
    }

    @Override
    @JsonIgnore
    public String getId() {
        return getBody().getRcuId();
    }

    public MecHeader getHeader() {
        return header;
    }

    public MecBody getBody() {
        return body;
    }

    public long getTimestampOfDevOut() {
        return timestampOfDevOut;
    }

    public void setHeader(MecHeader header) {
        this.header = header;
    }

    public void setBody(MecBody body) {
        this.body = body;
    }

    public void setTimestampOfDevOut(long timestampOfDevOut) {
        this.timestampOfDevOut = timestampOfDevOut;
    }

    public static class MecHeader {

        private String ver;
        private String prefix;
        private String dataCategory;
        private String ctl;
        private long dataLen;
        private long timestamp;

        public MecHeader() {

        }

        public MecHeader(String ver, String prefix, String dataCategory, String ctl, long dataLen, long timestamp) {
            this.ver = ver;
            this.prefix = prefix;
            this.dataCategory = dataCategory;
            this.ctl = ctl;
            this.dataLen = dataLen;
            this.timestamp = timestamp;
        }

        public String getVer() {
            return ver;
        }

        public String getPrefix() {
            return prefix;
        }

        public String getDataCategory() {
            return dataCategory;
        }

        public String getCtl() {
            return ctl;
        }

        public long getDataLen() {
            return dataLen;
        }

        public long getTimestamp() {
            return timestamp;
        }

        public void setVer(String ver) {
            this.ver = ver;
        }

        public void setPrefix(String prefix) {
            this.prefix = prefix;
        }

        public void setDataCategory(String dataCategory) {
            this.dataCategory = dataCategory;
        }

        public void setCtl(String ctl) {
            this.ctl = ctl;
        }

        public void setDataLen(long dataLen) {
            this.dataLen = dataLen;
        }

        public void setTimestamp(long timestamp) {
            this.timestamp = timestamp;
        }

        public static class Builder {
            private String ver;
            private String prefix;
            private String dataCategory;
            private String ctl;
            private Long dataLen;
            private Long timestamp;

            public Builder setVer(String ver) {
                this.ver = ver;
                return this;
            }

            public Builder setPrefix(String prefix) {
                this.prefix = prefix;
                return this;
            }

            public Builder setDataCategory(String dataCategory) {
                this.dataCategory = dataCategory;
                return this;
            }

            public Builder setCtl(String ctl) {
                this.ctl = ctl;
                return this;
            }

            public Builder setDataLen(long dataLen) {
                this.dataLen = dataLen;
                return this;
            }

            public Builder setTimestamp(long timestamp) {
                this.timestamp = timestamp;
                return this;
            }

            public Builder defaultValue() {
                if (StringUtils.isBlank(this.ver)) {
                    this.ver = "01";
                }
                if (StringUtils.isBlank(this.prefix)) {
                    this.prefix = "F2";
                }
                if (StringUtils.isBlank(this.dataCategory)) {
                    this.dataCategory = "79";
                }
                if (StringUtils.isBlank(this.ctl)) {
                    this.ctl = "00";
                }
                if (this.dataLen == null) {
                    this.dataLen = 224L;
                }
                if (this.timestamp == null) {
                    this.timestamp = System.currentTimeMillis();
                }
                return this;
            }

            public MecHeader build() {
                return new MecHeader(this.ver, this.prefix, this.dataCategory, this.ctl, this.dataLen, this.timestamp);
            }
        }
    }

    public static class MecBody {
        private long deviceType;
        private int objectiveNum;
        private long timestampOfDetOut;
        private long gnssType;
        private long timestampOfDetIn;
        private String bytes;
        private String rcuId;
        private String body;
        private String deviceId;
        private long channelId;
        private List<MecObjective> objective;

        public MecBody() {

        }

        public MecBody(long deviceType, int objectiveNum, long timestampOfDetOut, long gnssType, long timestampOfDetIn,
                       String bytes, String rcuId, String body, String deviceId, long channelId, List<MecObjective> objective) {
            this.deviceType = deviceType;
            this.objectiveNum = objectiveNum;
            this.timestampOfDetOut = timestampOfDetOut;
            this.gnssType = gnssType;
            this.timestampOfDetIn = timestampOfDetIn;
            this.bytes = bytes;
            this.rcuId = rcuId;
            this.body = body;
            this.deviceId = deviceId;
            this.channelId = channelId;
            this.objective = objective;
        }

        public long getDeviceType() {
            return deviceType;
        }

        public int getObjectiveNum() {
            return objectiveNum;
        }

        public long getTimestampOfDetOut() {
            return timestampOfDetOut;
        }

        public long getGnssType() {
            return gnssType;
        }

        public long getTimestampOfDetIn() {
            return timestampOfDetIn;
        }

        public String getBytes() {
            return bytes;
        }

        public String getRcuId() {
            return rcuId;
        }

        public String getBody() {
            return body;
        }

        public String getDeviceId() {
            return deviceId;
        }

        public long getChannelId() {
            return channelId;
        }

        public List<MecObjective> getObjective() {
            return objective;
        }

        public void setDeviceType(long deviceType) {
            this.deviceType = deviceType;
        }

        public void setObjectiveNum(int objectiveNum) {
            this.objectiveNum = objectiveNum;
        }

        public void setTimestampOfDetOut(long timestampOfDetOut) {
            this.timestampOfDetOut = timestampOfDetOut;
        }

        public void setGnssType(long gnssType) {
            this.gnssType = gnssType;
        }

        public void setTimestampOfDetIn(long timestampOfDetIn) {
            this.timestampOfDetIn = timestampOfDetIn;
        }

        public void setBytes(String bytes) {
            this.bytes = bytes;
        }

        public void setRcuId(String rcuId) {
            this.rcuId = rcuId;
        }

        public void setBody(String body) {
            this.body = body;
        }

        public void setDeviceId(String deviceId) {
            this.deviceId = deviceId;
        }

        public void setChannelId(long channelId) {
            this.channelId = channelId;
        }

        public void setObjective(List<MecObjective> objective) {
            this.objective = objective;
        }

        public static class Builder {
            private Long deviceType;
            private Integer objectiveNum;
            private Long timestampOfDetOut;
            private Long gnssType;
            private Long timestampOfDetIn;
            private String bytes;
            private String rcuId;
            private String body;
            private String deviceId;
            private Long channelId;
            private List<MecObjective> objective;

            public Builder setDeviceType(Long deviceType) {
                this.deviceType = deviceType;
                return this;
            }

            public Builder setObjectiveNum(Integer objectiveNum) {
                this.objectiveNum = objectiveNum;
                return this;
            }

            public Builder setTimestampOfDetOut(Long timestampOfDetOut) {
                this.timestampOfDetOut = timestampOfDetOut;
                return this;
            }

            public Builder setTimestampOfDetIn(Long timestampOfDetIn) {
                this.timestampOfDetIn = timestampOfDetIn;
                return this;
            }

            public Builder setBytes(String bytes) {
                this.bytes = bytes;
                return this;
            }

            public Builder setRcuId(String rcuId) {
                this.rcuId = rcuId;
                return this;
            }

            public Builder setBody(String body) {
                this.body = body;
                return this;
            }

            public Builder setDeviceId(String deviceId) {
                this.deviceId = deviceId;
                return this;
            }

            public Builder setChannelId(long channelId) {
                this.channelId = channelId;
                return this;
            }

            public Builder setObjective(List<MecObjective> objective) {
                this.objective = objective;
                return this;
            }


            public Builder defaultValue() {
                if (deviceType == null) {
                    this.deviceType = 1L;
                }
                if (timestampOfDetOut == null) {
                    timestampOfDetOut = System.currentTimeMillis();
                }
                if (gnssType == null) {
                    gnssType = 2L;
                }
                if (timestampOfDetIn == null) {
                    timestampOfDetIn = System.currentTimeMillis();
                }
                if (bytes == null) {
                    bytes = "";
                }
                if (rcuId == null) {
                    rcuId = "";
                }
                if (body == null) {
                    body = "";
                }
                if (deviceId == null) {
                    deviceId = "0000000000000000000000";
                }
                if (channelId == null) {
                    channelId = 1L;
                }
                if (objective == null) {
                    objective = new ArrayList<>();
                }
                objectiveNum = objective.size();
                return this;
            }

            public MecBody build() {
                return new MecBody(this.deviceType, this.objectiveNum, this.timestampOfDetOut, this.gnssType, this.timestampOfDetIn,
                        this.bytes, this.rcuId, this.body, this.deviceId, this.channelId, this.objective);
            }
        }
    }

    public static class MecObjective {
        private double accelVert;
        private int accelVertConfidence;
        private int elevConfidence;
        private int elevation;
        private FilterInfo filterInfo;
        private int filterInfoType;
        private int headConfidence;
        private double heading;
        private double height;
        private int histLocNum;
        private List<Loc> histLocs;
        private int laneId;
        private double locEast;
        private double locNorth;
        private double latitude;
        private double len;
        private double longitude;
        private int objColor;
        private int objId;
        private int plateColor;
        private String plateNo;
        private int plateType;
        private int posConfidence;
        private int predLocNum;
        private List<Loc> predLocs;
        private int size;
        private double speed;
        private int speedConfidence;
        private double speedEast;
        private int speedEastConfidence;
        private double speedNorth;
        private int speedNorthConfidence;
        private int status;
        private long trackedTimes;
        private int type;
        private String uuid;
        private double width;

        public MecObjective() {

        }

        public MecObjective(double accelVert, int accelVertConfidence, int elevConfidence, int elevation, FilterInfo filterInfo,
                            int filterInfoType, int headConfidence, double heading, double height, int histLocNum, List<Loc> histLocs,
                            int laneId, double locEast, double locNorth, double latitude, double len, double longitude, int objColor,
                            int objId, int plateColor, String plateNo, int plateType, int posConfidence, int predLocNum, List<Loc> predLocs,
                            int size, double speed, int speedConfidence, double speedEast, int speedEastConfidence, double speedNorth,
                            int speedNorthConfidence, int status, long trackedTimes, String uuid, double width) {
            this.accelVert = accelVert;
            this.accelVertConfidence = accelVertConfidence;
            this.elevConfidence = elevConfidence;
            this.elevation = elevation;
            this.filterInfo = filterInfo;
            this.filterInfoType = filterInfoType;
            this.headConfidence = headConfidence;
            this.heading = heading;
            this.height = height;
            this.histLocNum = histLocNum;
            this.histLocs = histLocs;
            this.laneId = laneId;
            this.locEast = locEast;
            this.locNorth = locNorth;
            this.latitude = latitude;
            this.len = len;
            this.longitude = longitude;
            this.objColor = objColor;
            this.objId = objId;
            this.plateColor = plateColor;
            this.plateNo = plateNo;
            this.plateType = plateType;
            this.posConfidence = posConfidence;
            this.predLocNum = predLocNum;
            this.predLocs = predLocs;
            this.size = size;
            this.speed = speed;
            this.speedConfidence = speedConfidence;
            this.speedEast = speedEast;
            this.speedEastConfidence = speedEastConfidence;
            this.speedNorth = speedNorth;
            this.speedNorthConfidence = speedNorthConfidence;
            this.status = status;
            this.trackedTimes = trackedTimes;
            this.type = len > 7000 ? 7 : 2;;
            this.uuid = uuid;
            this.width = width;
        }

        public double getAccelVert() {
            return accelVert;
        }

        public int getAccelVertConfidence() {
            return accelVertConfidence;
        }

        public int getElevConfidence() {
            return elevConfidence;
        }

        public int getElevation() {
            return elevation;
        }

        public FilterInfo getFilterInfo() {
            return filterInfo;
        }

        public int getFilterInfoType() {
            return filterInfoType;
        }

        public int getHeadConfidence() {
            return headConfidence;
        }

        public double getHeading() {
            return heading;
        }

        public double getHeight() {
            return height;
        }

        public int getHistLocNum() {
            return histLocNum;
        }

        public List<Loc> getHistLocs() {
            return histLocs;
        }

        public int getLaneId() {
            return laneId;
        }

        public double getLocEast() {
            return locEast;
        }

        public double getLocNorth() {
            return locNorth;
        }

        public double getLatitude() {
            return latitude;
        }

        public double getLen() {
            return len;
        }

        public double getLongitude() {
            return longitude;
        }

        public int getObjColor() {
            return objColor;
        }

        public int getObjId() {
            return objId;
        }

        public int getPlateColor() {
            return plateColor;
        }

        public String getPlateNo() {
            return plateNo;
        }

        public int getPlateType() {
            return plateType;
        }

        public int getPosConfidence() {
            return posConfidence;
        }

        public int getPredLocNum() {
            return predLocNum;
        }

        public List<Loc> getPredLocs() {
            return predLocs;
        }

        public int getSize() {
            return size;
        }

        public double getSpeed() {
            return speed;
        }

        public int getSpeedConfidence() {
            return speedConfidence;
        }

        public double getSpeedEast() {
            return speedEast;
        }

        public int getSpeedEastConfidence() {
            return speedEastConfidence;
        }

        public double getSpeedNorth() {
            return speedNorth;
        }

        public int getSpeedNorthConfidence() {
            return speedNorthConfidence;
        }

        public int getStatus() {
            return status;
        }

        public long getTrackedTimes() {
            return trackedTimes;
        }

        public int getType() {
            return type;
        }

        public String getUuid() {
            return uuid;
        }

        public double getWidth() {
            return width;
        }

        public void setAccelVert(double accelVert) {
            this.accelVert = accelVert;
        }

        public void setAccelVertConfidence(int accelVertConfidence) {
            this.accelVertConfidence = accelVertConfidence;
        }

        public void setElevConfidence(int elevConfidence) {
            this.elevConfidence = elevConfidence;
        }

        public void setElevation(int elevation) {
            this.elevation = elevation;
        }

        public void setFilterInfo(FilterInfo filterInfo) {
            this.filterInfo = filterInfo;
        }

        public void setFilterInfoType(int filterInfoType) {
            this.filterInfoType = filterInfoType;
        }

        public void setHeadConfidence(int headConfidence) {
            this.headConfidence = headConfidence;
        }

        public void setHeading(double heading) {
            this.heading = heading;
        }

        public void setHeight(double height) {
            this.height = height;
        }

        public void setHistLocNum(int histLocNum) {
            this.histLocNum = histLocNum;
        }

        public void setHistLocs(List<Loc> histLocs) {
            this.histLocs = histLocs;
        }

        public void setLaneId(int laneId) {
            this.laneId = laneId;
        }

        public void setLocEast(double locEast) {
            this.locEast = locEast;
        }

        public void setLocNorth(double locNorth) {
            this.locNorth = locNorth;
        }

        public void setLatitude(double latitude) {
            this.latitude = latitude;
        }

        public void setLen(double len) {
            this.len = len;
        }

        public void setLongitude(double longitude) {
            this.longitude = longitude;
        }

        public void setObjColor(int objColor) {
            this.objColor = objColor;
        }

        public void setObjId(int objId) {
            this.objId = objId;
        }

        public void setPlateColor(int plateColor) {
            this.plateColor = plateColor;
        }

        public void setPlateNo(String plateNo) {
            this.plateNo = plateNo;
        }

        public void setPlateType(int plateType) {
            this.plateType = plateType;
        }

        public void setPosConfidence(int posConfidence) {
            this.posConfidence = posConfidence;
        }

        public void setPredLocNum(int predLocNum) {
            this.predLocNum = predLocNum;
        }

        public void setPredLocs(List<Loc> predLocs) {
            this.predLocs = predLocs;
        }

        public void setSize(int size) {
            this.size = size;
        }

        public void setSpeed(double speed) {
            this.speed = speed;
        }

        public void setSpeedConfidence(int speedConfidence) {
            this.speedConfidence = speedConfidence;
        }

        public void setSpeedEast(double speedEast) {
            this.speedEast = speedEast;
        }

        public void setSpeedEastConfidence(int speedEastConfidence) {
            this.speedEastConfidence = speedEastConfidence;
        }

        public void setSpeedNorth(double speedNorth) {
            this.speedNorth = speedNorth;
        }

        public void setSpeedNorthConfidence(int speedNorthConfidence) {
            this.speedNorthConfidence = speedNorthConfidence;
        }

        public void setStatus(int status) {
            this.status = status;
        }

        public void setTrackedTimes(long trackedTimes) {
            this.trackedTimes = trackedTimes;
        }

        public void setType(int type) {
            this.type = type;
        }

        public void setUuid(String uuid) {
            this.uuid = uuid;
        }

        public void setWidth(double width) {
            this.width = width;
        }

        public static class Builder {
            private double accelVert;
            private int accelVertConfidence;
            private int elevConfidence;
            private int elevation;
            private FilterInfo filterInfo;
            private int filterInfoType;
            private int headConfidence;
            private double heading;
            private double height;
            private int histLocNum;
            private List<Loc> histLocs;
            private int laneId;
            private double locEast;
            private double locNorth;
            private double latitude;
            private double len;
            private double longitude;
            private int objColor;
            private int objId;
            private int plateColor;
            private String plateNo;
            private int plateType;
            private int posConfidence;
            private int predLocNum;
            private List<Loc> predLocs;
            private int size;
            private double speed;
            private int speedConfidence;
            private double speedEast;
            private int speedEastConfidence;
            private double speedNorth;
            private int speedNorthConfidence;
            private int status;
            private long trackedTimes;
            private int type;
            private String uuid;
            private double width;

            public Builder setAccelVert(double accelVert) {
                this.accelVert = accelVert;
                return this;
            }

            public Builder setHeading(double heading) {
                this.heading = heading;
                return this;
            }

            public Builder setHeight(double height) {
                this.height = 100 * height;
                return this;
            }

            public Builder setLatitude(double latitude) {
                this.latitude = latitude;
                return this;
            }

            public Builder setLength(double len) {
                this.len = 100 * len;
                return this;
            }

            public Builder setLongitude(double longitude) {
                this.longitude = longitude;
                return this;
            }

            public Builder setObjId(int objId) {
                this.objId = objId;
                return this;
            }

            public Builder setSpeed(double speed) {
                this.speed = speed;
                return this;
            }

            public Builder setUuid(String uuid) {
                this.uuid = uuid;
                return this;
            }

            public Builder setWidth(double width) {
                this.width = 100 * width;
                return this;
            }

            public Builder defaultValue() {
                this.accelVertConfidence = 1;
                this.elevConfidence = 0;
                this.elevation = 5000;
                this.filterInfo = new FilterInfo();
                this.filterInfoType = 0;
                this.headConfidence = 0;
                this.histLocNum = 0;
                this.histLocs = new ArrayList<>();
                this.laneId = 1;
                this.locEast = 0d;
                this.locNorth = 0d;
                this.objColor = 255;
                this.plateColor = 255;
                this.plateNo = "沪A12345";
                this.plateType = 255;
                this.posConfidence = 9;
                this.predLocNum = 0;
                this.predLocs = new ArrayList<>();
                this.size = 390;
                this.speedConfidence = 3;
                this.speedEast = -3.54;
                this.speedEastConfidence = 3;
                this.speedNorth = -12.69;
                this.speedNorthConfidence = 3;
                this.status = 1;
                this.trackedTimes = 9303;
                return this;
            }

            public MecObjective build() {
                return new MecObjective(this.accelVert, this.accelVertConfidence, this.elevConfidence, this.elevation, this.filterInfo,
                        this.filterInfoType, this.headConfidence, this.heading, this.height, this.histLocNum, this.histLocs,
                        this.laneId, this.locEast, this.locNorth, this.latitude, this.len, this.longitude, this.objColor,
                        this.objId, this.plateColor, this.plateNo, this.plateType, this.posConfidence, this.predLocNum, this.predLocs,
                        this.size, this.speed, this.speedConfidence, this.speedEast, this.speedEastConfidence, this.speedNorth,
                        this.speedNorthConfidence, this.status, this.trackedTimes, this.uuid, this.width);
            }
        }
    }

    @JsonIgnoreProperties
    public static class FilterInfo {

    }

    @JsonIgnoreProperties
    public static class Loc {

    }
}
